import { isBrowser } from '@vue-devtools/shared-utils'
import type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'
import type { ComponentBounds, ComponentInstance } from '@vue/devtools-api'
import { JobQueue } from './util/queue'

let overlay: HTMLDivElement
let overlayContent: HTMLDivElement
let currentInstance

function createOverlay() {
  if (overlay || !isBrowser) {
    return
  }
  overlay = document.createElement('div')
  overlay.style.backgroundColor = 'rgba(65, 184, 131, 0.35)'
  overlay.style.position = 'fixed'
  overlay.style.zIndex = '99999999999998'
  overlay.style.pointerEvents = 'none'
  overlay.style.borderRadius = '3px'
  overlayContent = document.createElement('div')
  overlayContent.style.position = 'fixed'
  overlayContent.style.zIndex = '99999999999999'
  overlayContent.style.pointerEvents = 'none'
  overlayContent.style.backgroundColor = 'white'
  overlayContent.style.fontFamily = 'monospace'
  overlayContent.style.fontSize = '11px'
  overlayContent.style.padding = '4px 8px'
  overlayContent.style.borderRadius = '3px'
  overlayContent.style.color = '#333'
  overlayContent.style.textAlign = 'center'
  overlayContent.style.border = 'rgba(65, 184, 131, 0.5) 1px solid'
  overlayContent.style.backgroundClip = 'padding-box'
}

// Use a job queue to preserve highlight/unhighlight calls order
// This prevents "sticky" highlights that are not removed because highlight is async
const jobQueue = new JobQueue()

export async function highlight(instance: ComponentInstance, backend: DevtoolsBackend, ctx: BackendContext) {
  await jobQueue.queue('highlight', async () => {
    if (!instance) {
      return
    }

    const bounds = await backend.api.getComponentBounds(instance)
    if (bounds) {
      createOverlay()

      // Name
      const name = (await backend.api.getComponentName(instance)) || 'Anonymous'
      const pre = document.createElement('span')
      pre.style.opacity = '0.6'
      pre.textContent = '<'
      const text = document.createElement('span')
      text.style.fontWeight = 'bold'
      text.style.color = '#09ab56'
      text.textContent = name
      const post = document.createElement('span')
      post.style.opacity = '0.6'
      post.textContent = '>'

      // Size
      const size = document.createElement('span')
      size.style.opacity = '0.5'
      size.style.marginLeft = '6px'
      size.appendChild(document.createTextNode((Math.round(bounds.width * 100) / 100).toString()))
      const multiply = document.createElement('span')
      multiply.style.marginLeft = multiply.style.marginRight = '2px'
      multiply.textContent = '×'
      size.appendChild(multiply)
      size.appendChild(document.createTextNode((Math.round(bounds.height * 100) / 100).toString()))

      currentInstance = instance

      await showOverlay(bounds, [pre, text, post, size])
    }

    startUpdateTimer(backend, ctx)
  })
}

export async function unHighlight() {
  await jobQueue.queue('unHighlight', async () => {
    overlay?.parentNode?.removeChild(overlay)
    overlayContent?.parentNode?.removeChild(overlayContent)
    currentInstance = null

    stopUpdateTimer()
  })
}

function showOverlay(bounds: ComponentBounds, children: Node[] = null) {
  if (!isBrowser || !children.length) {
    return
  }

  positionOverlay(bounds)
  document.body.appendChild(overlay)

  overlayContent.innerHTML = ''
  children.forEach(child => overlayContent.appendChild(child))
  document.body.appendChild(overlayContent)

  positionOverlayContent(bounds)
}

function positionOverlay({ width = 0, height = 0, top = 0, left = 0 }) {
  overlay.style.width = `${Math.round(width)}px`
  overlay.style.height = `${Math.round(height)}px`
  overlay.style.left = `${Math.round(left)}px`
  overlay.style.top = `${Math.round(top)}px`
}

function positionOverlayContent({ height = 0, top = 0, left = 0 }) {
  // Content position (prevents overflow)
  const contentWidth = overlayContent.offsetWidth
  const contentHeight = overlayContent.offsetHeight
  let contentLeft = left
  if (contentLeft < 0) {
    contentLeft = 0
  }
  else if (contentLeft + contentWidth > window.innerWidth) {
    contentLeft = window.innerWidth - contentWidth
  }
  let contentTop = top - contentHeight - 2
  if (contentTop < 0) {
    contentTop = top + height + 2
  }
  if (contentTop < 0) {
    contentTop = 0
  }
  else if (contentTop + contentHeight > window.innerHeight) {
    contentTop = window.innerHeight - contentHeight
  }
  overlayContent.style.left = `${~~contentLeft}px`
  overlayContent.style.top = `${~~contentTop}px`
}

async function updateOverlay(backend: DevtoolsBackend, _ctx: BackendContext) {
  if (currentInstance) {
    const bounds = await backend.api.getComponentBounds(currentInstance)
    if (bounds) {
      const sizeEl = overlayContent.children.item(3)
      const widthEl = sizeEl.childNodes[0] as unknown as Text
      widthEl.textContent = (Math.round(bounds.width * 100) / 100).toString()
      const heightEl = sizeEl.childNodes[2] as unknown as Text
      heightEl.textContent = (Math.round(bounds.height * 100) / 100).toString()

      positionOverlay(bounds)
      positionOverlayContent(bounds)
    }
  }
}

let updateTimer

function startUpdateTimer(backend: DevtoolsBackend, ctx: BackendContext) {
  stopUpdateTimer()
  updateTimer = setInterval(() => {
    jobQueue.queue('updateOverlay', async () => {
      await updateOverlay(backend, ctx)
    })
  }, 1000 / 30) // 30fps
}

function stopUpdateTimer() {
  clearInterval(updateTimer)
}
